package com.example.codediff.service;

import com.example.codediff.analyzer.ClassAnalyzer;
import com.example.codediff.analyzer.MethodDiffAnalyzer;
import com.example.codediff.model.CompareResult;
import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.stream.Collectors;

/**
 * JAR包比对服务
 * 负责解析JAR文件并进行差异比对
 */
@Slf4j
@Service
public class JarCompareService {

    /**
     * 比对两个JAR包的差异
     * @param oldJarFile 旧版本JAR文件
     * @param newJarFile 新版本JAR文件
     * @param packagePrefix 包前缀，用于过滤需要比对的类
     * @return 返回比对结果，包含新增、删除和修改的类信息
     */
    private Path lastNewJarPath;

    /**
     * 获取最后一次比对的新版本JAR文件路径
     */
    public Path getLastNewJarPath() {
        return lastNewJarPath;
    }

    @Autowired
    private CompareResultCache cache;

    @Autowired
    private MethodDiffAnalyzer methodDiffAnalyzer;

    public CompareResult compareJars(MultipartFile oldJarFile, MultipartFile newJarFile, String packagePrefix) throws IOException {
        log.info("开始比较JAR包: oldJar={}, newJar={}, packagePrefix={}", 
            oldJarFile.getOriginalFilename(), newJarFile.getOriginalFilename(), packagePrefix);

        Path oldJarPath = null;
        Path newJarPath = null;
        
        try {
            // 创建临时文件
            oldJarPath = createTempFile(oldJarFile);
            newJarPath = createTempFile(newJarFile);
            this.lastNewJarPath = newJarPath;
            
            log.debug("临时文件已创建: oldJar={}, newJar={}", oldJarPath, newJarPath);

            // 比较两个JAR包
            CompareResult result = doCompare(oldJarPath, newJarPath, packagePrefix);
            
            // 缓存比对结果
            String cacheKey = cache.generateKey(
                oldJarFile.getOriginalFilename(),
                newJarFile.getOriginalFilename(),
                packagePrefix
            );
            cache.put(cacheKey, result, newJarPath);
            
            return result;
        } finally {
            // 只删除旧版本的临时文件，新版本的留着给调用链分析用
            if (oldJarPath != null) {
                Files.deleteIfExists(oldJarPath);
            }
        }
    }

    private CompareResult doCompare(Path oldJarPath, Path newJarPath, String packagePrefix) throws IOException {
        CompareResult result = new CompareResult();
        Set<String> allClasses = listClasses(oldJarPath.toString(), newJarPath.toString(), packagePrefix);

        // 分析类级别的变更
        for (String className : allClasses) {
            ClassNode oldClass = methodDiffAnalyzer.readClass(oldJarPath.toString(), className);
            ClassNode newClass = methodDiffAnalyzer.readClass(newJarPath.toString(), className);

            if (oldClass == null && newClass != null) {
                result.getAddedClasses().add(className);
            } else if (oldClass != null && newClass == null) {
                result.getRemovedClasses().add(className);
            } else if (oldClass != null && newClass != null) {
                // 分析方法级别的变更
                analyzeMethodChanges(className, oldClass, newClass, result);
            }
        }

        return result;
    }

    private void analyzeMethodChanges(String className, ClassNode oldClass, ClassNode newClass, CompareResult result) {
        // 将方法列表转换为Map以便快速查找
        Map<String, MethodNode> oldMethods = new HashMap<>();
        Map<String, MethodNode> newMethods = new HashMap<>();

        oldClass.methods.forEach(m -> oldMethods.put(m.name, m));
        newClass.methods.forEach(m -> newMethods.put(m.name, m));

        // 检查方法的变更
        Set<String> allMethodNames = new HashSet<>();
        allMethodNames.addAll(oldMethods.keySet());
        allMethodNames.addAll(newMethods.keySet());

        boolean hasChanges = false;
        for (String methodName : allMethodNames) {
            MethodNode oldMethod = oldMethods.get(methodName);
            MethodNode newMethod = newMethods.get(methodName);

            if (oldMethod == null && newMethod != null) {
                // 新增的方法
                result.addMethodDiff(className, methodName, newMethod.desc, "新增");
                hasChanges = true;
            } else if (oldMethod != null && newMethod == null) {
                // 删除的方法
                result.addMethodDiff(className, methodName, oldMethod.desc, "删除");
                hasChanges = true;
            } else if (oldMethod != null && newMethod != null) {
                // 比较方法的实现
                if (!Arrays.equals(methodDiffAnalyzer.getMethodBytecode(oldMethod),
                                 methodDiffAnalyzer.getMethodBytecode(newMethod))) {
                    result.addMethodDiff(className, methodName, newMethod.desc, "修改");
                    hasChanges = true;
                }
            }
        }

        if (hasChanges) {
            result.getModifiedClasses().add(className);
        }
    }

    private Set<String> listClasses(String oldJarPath, String newJarPath, String packagePrefix) throws IOException {
        Set<String> allClasses = new HashSet<>();

        try (ZipFile oldZip = new ZipFile(oldJarPath);
             ZipFile newZip = new ZipFile(newJarPath)) {

            // 遍历ZIP文件中的所有条目
            Enumeration<? extends ZipEntry> oldEntries = oldZip.entries();
            while (oldEntries.hasMoreElements()) {
                ZipEntry entry = oldEntries.nextElement();
                String entryName = entry.getName();

                if (!entry.isDirectory() && entryName.endsWith(".class")) {
                    // 获取实际的类路径
                    String classPath = entryName;
                    if (classPath.startsWith("BOOT-INF/classes/")) {
                        classPath = classPath.substring("BOOT-INF/classes/".length());
                    }

                    // 检查是否匹配包前缀
                    if (classPath.startsWith(packagePrefix.replace('.', '/') + "/")) {
                        allClasses.add(classPath.replace('/', '.').replace(".class", ""));
                    }
                }
            }

            Enumeration<? extends ZipEntry> newEntries = newZip.entries();
            while (newEntries.hasMoreElements()) {
                ZipEntry entry = newEntries.nextElement();
                String entryName = entry.getName();

                if (!entry.isDirectory() && entryName.endsWith(".class")) {
                    // 获取实际的类路径
                    String classPath = entryName;
                    if (classPath.startsWith("BOOT-INF/classes/")) {
                        classPath = classPath.substring("BOOT-INF/classes/".length());
                    }

                    // 检查是否匹配包前缀
                    if (classPath.startsWith(packagePrefix.replace('.', '/') + "/")) {
                        allClasses.add(classPath.replace('/', '.').replace(".class", ""));
                    }
                }
            }
        }

        return allClasses;
    }

    private Path createTempFile(MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename();
        log.debug("创建临时文件: {}", originalFilename);
        
        Path tempFile = Files.createTempFile("jar_compare_", "_" + originalFilename);
        file.transferTo(tempFile.toFile());
        
        return tempFile;
    }

    /**
     * 存储类的完整信息（方法和常量）
     */
    private static class ClassInfo {
        private final ClassAnalyzer.MethodInfo[] methods;
        private final ClassAnalyzer.ConstantInfo[] constants;

        public ClassInfo(ClassAnalyzer.MethodInfo[] methods, ClassAnalyzer.ConstantInfo[] constants) {
            this.methods = methods;
            this.constants = constants;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            ClassInfo that = (ClassInfo) obj;
            return Arrays.equals(methods, that.methods) &&
                   Arrays.equals(constants, that.constants);
        }

        @Override
        public int hashCode() {
            int result = Arrays.hashCode(methods);
            result = 31 * result + Arrays.hashCode(constants);
            return result;
        }
    }
}
